// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace SnapObjects.Expressions
{
    public abstract class MethodMetaParser<TData, TParam>
    {
        public virtual IDictionary<string, MethodMetadata<TData, TParam>> GetMethodMeta(
            Type type, object instance = null)
        {
            var result = new ConcurrentDictionary<string, MethodMetadata<TData, TParam>>(
                StringComparer.OrdinalIgnoreCase);

            foreach (var method in type.GetMethods())
            {
                if (this.NeedParser(method))
                {
                    var parameters = method.GetParameters();

                    if (parameters.Any(x => x.ParameterType.IsByRef))
                    {
                        continue;
                    }

                    MethodDataInfo<TData, TParam> getMethodInfo(Type[] typeArgs)
                    {
                        var methodInfo = method;

                        if (method.IsGenericMethod)
                        {
                            methodInfo = method.MakeGenericMethod(typeArgs);
                        }
                        var paramAttr = typeof(ParamArrayAttribute);
                        var paramList = new List<MethodParamInfo<TParam>>();

                        var optionalParamIndex = -1;
                        var variableParamIndex = -1;

                        foreach (var item in parameters)
                        {
                            if (item.IsOptional)
                            {
                                variableParamIndex = item.Position;
                            }
                            else if (item.GetCustomAttribute(paramAttr) != null)
                            {
                                optionalParamIndex = item.Position;
                            }

                            var isNotNull = item.GetCustomAttribute<NotBeNullAttribute>() != null;

                            var param = this.GenerateParam(item, isNotNull);

                            paramList.Add(param);
                        }

                        return this.GenerateMethod(
                            methodInfo,
                            paramList,
                            variableParamIndex,
                            optionalParamIndex);
                    }

                    MethodDataInfo<TData, TParam> info = null;

                    if (method.IsGenericMethod)
                    {
                        info = new MethodDataInfo<TData, TParam>(method, getMethodInfo);
                    }
                    else
                    {
                        info = getMethodInfo(null);
                    }

                    if (result.TryGetValue(method.Name, out var metadata))
                    {
                        metadata.AddMethod(info);
                    }
                    else
                    {
                        metadata = new MethodMetadata<TData, TParam>(method.Name, info);

                        result.TryAdd(method.Name, metadata);
                    }
                }
            }

            return result;
        }

        protected virtual bool NeedParser(MethodInfo methodInfo)
        {
            return methodInfo.IsPublic && methodInfo.IsStatic;
        }

        protected abstract MethodParamInfo<TParam> GenerateParam(
            ParameterInfo parameter, bool isNotNull);

        protected virtual MethodDataInfo<TData, TParam> GenerateMethod(
            MethodInfo method,
            List<MethodParamInfo<TParam>> paramInfos,
            int variableParamIndex,
            int optionalParamIndex)
        {
            return default;
        }
    }
}
